package io.summer.basis.tool;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.SimpleMetadataReaderFactory;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.core.type.filter.TypeFilter;
import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author Moonlight
 */
@Slf4j
public abstract class MyReflectionTool {
  private static SimpleMetadataReaderFactory readerFactory;

  private static void initReaderFactory() {
    if (readerFactory == null) {
      readerFactory = new CachingMetadataReaderFactory(new PathMatchingResourcePatternResolver());
    }
  }

  public static List<Class<?>> getClassWithAnnotations(String basePackage, Class<? extends Annotation> annotationType) {
    return getClassWithAnnotations(basePackage, "**/*", annotationType);
  }

  public static List<Class<?>> getClassWithAnnotations(String basePackage, String pattern, Class<? extends Annotation> annotationType) {
    List<Class<?>> classes = new ArrayList<>();
    try {
      String locationPattern = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
        ClassUtils.convertClassNameToResourcePath(basePackage) + "/" + pattern + ".class";

      initReaderFactory();

      PathMatchingResourcePatternResolver resourcePatternResolver = (PathMatchingResourcePatternResolver) readerFactory.getResourceLoader();
      Resource[] resources = resourcePatternResolver.getResources(locationPattern);
      for (Resource resource : resources) {
        MetadataReader reader = readerFactory.getMetadataReader(resource);
        String classname = reader.getClassMetadata().getClassName();
        Class<?> clazz = ClassUtils.forName(classname, null);
        if (clazz.isAnnotationPresent(annotationType)) {
          classes.add(clazz);
        }
      }
    } catch (Exception e) {
      log.error("getClassWithAnnotations Exception: ", e);
    }
    return classes;
  }

  public static List<Method> getClassMethodsWithAnnotation(Class<?> clazz, Class<? extends Annotation> annotationType) {
    Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz);
    return Arrays.stream(methods).filter(method -> method.isAnnotationPresent(annotationType))
      .collect(Collectors.toList());
  }

  /**
   * 获取泛型类的参数类型
   *
   * @param clazz 必须是泛型类
   * @param index 第几个泛型参数，0 开始
   * @return
   */
  public static Type[] getGenericTypesOfClass(Class<?> clazz, int index) {
    Type genericInterface = clazz.getGenericInterfaces()[index];
    if (genericInterface instanceof ParameterizedType) {
      return ((ParameterizedType) genericInterface).getActualTypeArguments();
    }
    return new Type[0];
  }

  public static class ClassAndInterfacePathScanningCandidateComponentProvider extends ClassPathScanningCandidateComponentProvider {
    public ClassAndInterfacePathScanningCandidateComponentProvider(boolean useDefaultFilters) {
      super(useDefaultFilters);
    }

    @Override
    protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
      return true;
    }
  }

  /**
   * 获取具有指定注解的类和接口
   *
   * @param basePackage
   * @param includeFilter
   * @return
   */
  public static List<Class<?>> getClassWithAnnotations(String basePackage, TypeFilter... includeFilter) {
    final ClassPathScanningCandidateComponentProvider classScannerProvider =
      new ClassAndInterfacePathScanningCandidateComponentProvider(false);

    for (TypeFilter t : includeFilter) {
      classScannerProvider.addIncludeFilter(t);
    }

    List<Class<?>> classList = new ArrayList<>(10);
    for (BeanDefinition bd : classScannerProvider.findCandidateComponents(basePackage)) {
      try {
        classList.add(ClassUtils.forName(Objects.requireNonNull(bd.getBeanClassName()), null));
      } catch (ClassNotFoundException e) {
        log.error("getClassWithAnnotations Exception: ", e);
      }
    }
    return classList;
  }

  @Nullable
  public static AnnotationAttributes attributesFor(AnnotatedTypeMetadata metadata, Class<?> annotationClass) {
    return attributesFor(metadata, annotationClass.getName());
  }

  @Nullable
  public static AnnotationAttributes attributesFor(AnnotatedTypeMetadata metadata, String annotationClassName) {
    return AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(annotationClassName, false));
  }

  public static Set<AnnotationAttributes> attributesForRepeatable(AnnotationMetadata metadata,
                                                                  Class<?> containerClass, Class<?> annotationClass) {
    return attributesForRepeatable(metadata, containerClass.getName(), annotationClass.getName());
  }

  @SuppressWarnings("unchecked")
  public static Set<AnnotationAttributes> attributesForRepeatable(
    AnnotationMetadata metadata, String containerClassName, String annotationClassName) {

    Set<AnnotationAttributes> result = new LinkedHashSet<>();

    // Direct annotation present?
    addAttributesIfNotNull(result, metadata.getAnnotationAttributes(annotationClassName, false));

    // Container annotation present?
    Map<String, Object> container = metadata.getAnnotationAttributes(containerClassName, false);
    if (container != null && container.containsKey("value")) {
      for (Map<String, Object> containedAttributes : (Map<String, Object>[]) container.get("value")) {
        addAttributesIfNotNull(result, containedAttributes);
      }
    }

    // Return merged result
    return Collections.unmodifiableSet(result);
  }

  public final AnnotationMetadata getMetadata(Object source) {
    AnnotationMetadata metadata;
    if (source instanceof Class) {
      metadata = AnnotationMetadata.introspect((Class<?>) source);
    } else {
      metadata = ((MetadataReader) source).getAnnotationMetadata();
    }
    return metadata;
  }

  private static void addAttributesIfNotNull(
    Set<AnnotationAttributes> result, @Nullable Map<String, Object> attributes) {

    if (attributes != null) {
      result.add(AnnotationAttributes.fromMap(attributes));
    }
  }
}

